Avoid recursion in gtk_css_node_ensure_style()
authorAlexander Larsson <alexl@redhat.com>
Thu, 4 Jun 2020 10:10:31 +0000 (12:10 +0200)
committerAlexander Larsson <alexl@redhat.com>
Thu, 4 Jun 2020 10:10:31 +0000 (12:10 +0200)
gtk_css_node_ensure_style() recurses over previous siblings to ensure
these have a style before its following sibling.  As seen in
https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/2027 this can
cause us to stack overflow and crash if we have a lot of children.

And even if we don't have *that* many children its still somewhat
bad to have stack depths of the same magnitude as the number of
children, both for performance reasons and debuggability.

gtk/gtkcssnode.c

index 081662b0846bad5bb8729d15913a042062a222ac..9184ffe92807de4a1a4d0d58ea56bcd8b02e1b52 100644 (file)
@@ -949,25 +949,16 @@ gtk_css_node_needs_new_style (GtkCssNode *cssnode)
 }
 
 static void
-gtk_css_node_ensure_style (GtkCssNode                   *cssnode,
-                           const GtkCountingBloomFilter *filter,
-                           gint64                        current_time)
+gtk_css_node_do_ensure_style (GtkCssNode                   *cssnode,
+                              const GtkCountingBloomFilter *filter,
+                              gint64                        current_time)
 {
   gboolean style_changed;
 
-  if (!gtk_css_node_needs_new_style (cssnode))
-    return;
-
-  if (cssnode->parent)
-    gtk_css_node_ensure_style (cssnode->parent, filter, current_time);
-
   if (cssnode->style_is_invalid)
     {
       GtkCssStyle *new_style;
 
-      if (cssnode->previous_sibling)
-        gtk_css_node_ensure_style (cssnode->previous_sibling, filter, current_time);
-
       g_clear_pointer (&cssnode->cache, gtk_css_node_style_cache_unref);
 
       new_style = GTK_CSS_NODE_GET_CLASS (cssnode)->update_style (cssnode,
@@ -990,6 +981,36 @@ gtk_css_node_ensure_style (GtkCssNode                   *cssnode,
   cssnode->style_is_invalid = FALSE;
 }
 
+static void
+gtk_css_node_ensure_style (GtkCssNode                   *cssnode,
+                           const GtkCountingBloomFilter *filter,
+                           gint64                        current_time)
+{
+  GtkCssNode *sibling;
+
+  if (!gtk_css_node_needs_new_style (cssnode))
+    return;
+
+  if (cssnode->parent)
+    gtk_css_node_ensure_style (cssnode->parent, filter, current_time);
+
+  /* Ensure all siblings before this have a valid style, in order
+   * starting at the first that needs it. */
+  sibling = cssnode;
+  while (sibling->style_is_invalid &&
+         sibling->previous_sibling != NULL &&
+         gtk_css_node_needs_new_style (sibling->previous_sibling))
+    sibling = sibling->previous_sibling;
+
+  while (sibling != cssnode)
+    {
+      gtk_css_node_do_ensure_style (sibling, filter, current_time);
+      sibling = sibling->next_sibling;
+    }
+
+  gtk_css_node_do_ensure_style (cssnode, filter, current_time);
+}
+
 GtkCssStyle *
 gtk_css_node_get_style (GtkCssNode *cssnode)
 {